package com.remoter.api.util;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NetUtil {
	
	private static final Logger log = LoggerFactory.getLogger(NetUtil.class);
	private static volatile InetAddress LOCAL_ADDRESS = null;
	
	public static final String LOCALHOST = "127.0.0.1";
    public static final String ANYHOST = "0.0.0.0";
    public static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
    public static final Pattern ADDRESS_PATTERN = Pattern.compile("^\\d{1,3}(\\.\\d{1,3}){3}\\:\\d{1,5}$");
    
    public static boolean isValidAddress(String address){
    	return ADDRESS_PATTERN.matcher(address).matches();
    }
    
	public static boolean isValidAddress(InetAddress address){
		if(null == address || address.isLoopbackAddress()){
			return false;
		}
		String name = address.getHostAddress();
		return (name != null && !ANYHOST.equals(name) && !LOCALHOST.equals(name) && IP_PATTERN.matcher(name).matches());
	}
	
	public static InetAddress getLocalAddressByHostName(){
		try{
			InetAddress localAddress = InetAddress.getLocalHost();
			if(isValidAddress(localAddress)){
				return localAddress;
			}
		}catch(Throwable e){
			log.warn("failed to retriving local address by hostname: " + e);
		}
		return null;
	}
	
	public static InetAddress getLocalAddressBySocket(Map<String, Integer> destHostPorts){
		if(destHostPorts == null || destHostPorts.size() == 0){
			return null;
		}
		for(Entry<String, Integer> entry : destHostPorts.entrySet()){
			String host = entry.getKey();
			int port = entry.getValue();
			try{
				Socket socket = new Socket();
				try{
					SocketAddress addr = new InetSocketAddress(host,port);
					socket.connect(addr,1000);
					return socket.getLocalAddress();
				}finally{
					socket.close();
				}
			}catch(Throwable e){
				log.warn(String.format("failed to retriving local address by connecting to dest host:port(%s:%s) false, e=%s", host, port, e));
			}
		}
		return null;
	}
	
	public static InetAddress getLocalAddressByNetworkInterface(){
		try{
			Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
			if(null != interfaces){
				while(interfaces.hasMoreElements()){
					try{
						NetworkInterface network = interfaces.nextElement();
                        Enumeration<InetAddress> addresses = network.getInetAddresses();
                        while(addresses.hasMoreElements()){
                        	try{
                        		InetAddress address = addresses.nextElement();
                        		if(isValidAddress(address)){
                        			return address;
                        		}
                        	}catch(Throwable e){
                        		log.warn("failed to retriving ip address, " + e.getMessage(), e);
                        	}
                        }
					}catch(Throwable e){
						log.warn("failed to retriving ip address, " + e.getMessage(), e);
					}
				}
			}
		}catch(Throwable e){
			log.warn("failed to retriving ip address, " + e.getMessage(), e);
		}
		return null;
	}
	
	public static InetAddress getLocalAddress(Map<String,Integer> destHostPorts){
		if(LOCAL_ADDRESS != null){
			return LOCAL_ADDRESS;
		}
		InetAddress localAddress = getLocalAddressByHostName();
		if(!isValidAddress(localAddress)){
			localAddress = getLocalAddressBySocket(destHostPorts);
		}
		if(!isValidAddress(localAddress)){
			localAddress = getLocalAddressByNetworkInterface();
		}
		if(isValidAddress(localAddress)){
			LOCAL_ADDRESS = localAddress;
		}
		return localAddress;
	}
	
	public static InetAddress getLocalAddress(){
		return getLocalAddress(null);
	}
	
	public static int getAvailablePort(){
		try(ServerSocket serverSocket = new ServerSocket(0)){
			return serverSocket.getLocalPort();
		}catch(Exception e){
			return 0;
		}
	}
	
}